001 /* 002 /* 003 * Copyright 2006 Stephen J. McConnell. 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 014 * implied. 015 * 016 * See the License for the specific language governing permissions and 017 * limitations under the License. 018 */ 019 020 package net.dpml.util; 021 022 import java.net.URI; 023 import java.net.URL; 024 import java.net.URISyntaxException; 025 import java.util.Hashtable; 026 import java.util.Map; 027 import java.io.InputStream; 028 import java.io.InputStreamReader; 029 import java.io.IOException; 030 import java.io.OutputStream; 031 import java.io.FileNotFoundException; 032 033 import javax.xml.XMLConstants; 034 035 import net.dpml.transit.Artifact; 036 037 import org.w3c.dom.DOMError; 038 import org.w3c.dom.DOMLocator; 039 import org.w3c.dom.bootstrap.DOMImplementationRegistry; 040 import org.w3c.dom.DOMErrorHandler; 041 import org.w3c.dom.DOMConfiguration; 042 import org.w3c.dom.Document; 043 import org.w3c.dom.ls.DOMImplementationLS; 044 import org.w3c.dom.ls.LSOutput; 045 import org.w3c.dom.ls.LSParser; 046 import org.w3c.dom.ls.LSSerializer; 047 import org.w3c.dom.ls.LSResourceResolver; 048 import org.w3c.dom.ls.LSInput; 049 050 /** 051 * Utility class that creates a schema validating DOM3 parser. 052 */ 053 public class DOM3DocumentBuilder 054 { 055 private final Map m_map; 056 private final Logger m_logger; 057 058 /** 059 * Creation of a new DOM3 document builder. 060 */ 061 public DOM3DocumentBuilder() 062 { 063 this( new DefaultLogger( "dom" ) ); 064 } 065 066 /** 067 * Creation of a new DOM3 document builder. 068 * @param logger the assigned logging channel 069 */ 070 public DOM3DocumentBuilder( Logger logger ) 071 { 072 this( logger, new Hashtable() ); 073 } 074 075 /** 076 * Creation of a new DOM3 document builder. 077 * @param logger the assigned logging channel 078 * @param map namespace to builder uri map 079 */ 080 public DOM3DocumentBuilder( Logger logger, Map map ) 081 { 082 m_map = map; 083 m_logger = logger; 084 } 085 086 /** 087 * Parse an xml schema document. 088 * @param uri the document uri 089 * @return the validated document 090 * @exception IOException if an IO error occurs 091 */ 092 public Document parse( URI uri ) throws IOException 093 { 094 if( null == uri ) 095 { 096 throw new NullPointerException( "uri" ); 097 } 098 try 099 { 100 DOMImplementationRegistry registry = 101 DOMImplementationRegistry.newInstance(); 102 DOMImplementationLS impl = 103 (DOMImplementationLS) registry.getDOMImplementation( "LS" ); 104 if( null == impl ) 105 { 106 final String error = 107 "Unable to locate a DOM3 parser."; 108 throw new IllegalStateException( error ); 109 } 110 LSParser builder = impl.createLSParser( DOMImplementationLS.MODE_SYNCHRONOUS, null ); 111 DOMConfiguration config = builder.getDomConfig(); 112 config.setParameter( "error-handler", new InternalErrorHandler( m_logger, uri ) ); 113 config.setParameter( "resource-resolver", new InternalResourceResolver( m_map ) ); 114 config.setParameter( "validate", Boolean.TRUE ); 115 116 DOMInput input = new DOMInput(); 117 URL url = Artifact.toURL( uri ); 118 InputStream stream = url.openStream(); 119 InputStreamReader reader = new InputStreamReader( stream ); 120 input.setCharacterStream( reader ); 121 122 return builder.parse( input ); 123 } 124 catch( FileNotFoundException e ) 125 { 126 throw e; 127 } 128 catch( Throwable e ) 129 { 130 final String error = 131 "DOM3 error while attempting to parse document." 132 + "\nSource: " + uri; 133 //String message = ExceptionHelper.packException( error, e, true ); 134 IOException ioe = new IOException( error ); 135 ioe.initCause( e ); 136 throw ioe; 137 } 138 } 139 140 /** 141 * Write a document to an output stream. 142 * @param doc the document to write 143 * @param output the output stream 144 * @exception Exception if an error occurs 145 */ 146 public void write( Document doc, OutputStream output ) throws Exception 147 { 148 DOMImplementationRegistry registry = 149 DOMImplementationRegistry.newInstance(); 150 DOMImplementationLS impl = 151 (DOMImplementationLS) registry.getDOMImplementation( "LS" ); 152 if( null == impl ) 153 { 154 final String error = 155 "Unable to locate a DOM3 implementation."; 156 throw new IllegalStateException( error ); 157 } 158 LSSerializer domWriter = impl.createLSSerializer(); 159 LSOutput lsOut = impl.createLSOutput(); 160 lsOut.setByteStream( output ); 161 domWriter.write( doc, lsOut ); 162 } 163 164 /** 165 * Utility class to handle namespace uri resolves. 166 */ 167 private static class InternalResourceResolver implements LSResourceResolver 168 { 169 private final Map m_map; 170 171 /** 172 * Creation of a new InternalResourceResolver. 173 * @param map the namespace to builder map 174 */ 175 InternalResourceResolver( Map map ) 176 { 177 m_map = map; 178 } 179 180 /** 181 * Resolve an LS input. 182 * @param type the node type 183 * @param namespace the node namespace 184 * @param publicId the public id 185 * @param systemId the system id 186 * @param base the base value 187 * @return the LS input instance 188 */ 189 public LSInput resolveResource( 190 String type, String namespace, String publicId, String systemId, String base ) 191 { 192 if( XMLConstants.W3C_XML_SCHEMA_NS_URI.equals( type ) ) 193 { 194 DOMInput input = new DOMInput(); 195 input.setPublicId( publicId ); 196 input.setSystemId( systemId ); 197 input.setBaseURI( base ); 198 199 if( null == namespace ) 200 { 201 return input; 202 } 203 204 try 205 { 206 URI uri = resolveURI( namespace ); 207 URL url = Artifact.toURL( uri ); 208 InputStream stream = url.openStream(); 209 InputStreamReader reader = new InputStreamReader( stream ); 210 input.setCharacterStream( reader ); 211 } 212 catch( URISyntaxException e ) 213 { 214 // ignore 215 } 216 catch( IOException e ) 217 { 218 // ignore 219 } 220 return input; 221 } 222 else 223 { 224 return null; 225 } 226 } 227 228 private URI resolveURI( String namespace ) throws URISyntaxException 229 { 230 if( null == namespace ) 231 { 232 throw new NullPointerException( "namespace" ); 233 } 234 String value = System.getProperty( namespace ); 235 if( null != value ) 236 { 237 return new URI( value ); 238 } 239 if( m_map.containsKey( namespace ) ) 240 { 241 return (URI) m_map.get( namespace ); 242 } 243 else 244 { 245 return new URI( namespace ); 246 } 247 } 248 } 249 250 /** 251 * Internal error handler with lots of room for improvement. 252 */ 253 private static final class InternalErrorHandler implements DOMErrorHandler 254 { 255 private final URI m_uri; 256 private final Logger m_logger; 257 258 InternalErrorHandler( Logger logger, URI uri ) 259 { 260 m_uri = uri; 261 m_logger = logger; 262 } 263 264 /** 265 * Handle the supplied error. 266 * @param error the error 267 * @return a boolean value 268 */ 269 public boolean handleError( DOMError error ) 270 { 271 DOMLocator locator = error.getLocation(); 272 int line = locator.getLineNumber(); 273 int column = locator.getColumnNumber(); 274 String uri = locator.getUri(); 275 if( null == uri ) 276 { 277 uri = m_uri.toString(); 278 } 279 String position = "[" + line + ":" + column + "]"; 280 String message = error.getMessage(); 281 if( null != message ) 282 { 283 short severity = error.getSeverity(); 284 final String notice = 285 "DOM3 Validation Error" 286 + "\nSource: " 287 + uri + ":" + position 288 + "\nCause: " 289 + message; 290 if( severity == DOMError.SEVERITY_ERROR ) 291 { 292 m_logger.error( notice ); 293 } 294 else if( severity == DOMError.SEVERITY_WARNING ) 295 { 296 m_logger.warn( notice ); 297 } 298 else 299 { 300 m_logger.info( notice ); 301 } 302 } 303 return true; 304 } 305 306 private String getLabel( DOMError error ) 307 { 308 short severity = error.getSeverity(); 309 if( severity == DOMError.SEVERITY_ERROR ) 310 { 311 return "ERROR"; 312 } 313 else if( severity == DOMError.SEVERITY_WARNING ) 314 { 315 return "WARNING"; 316 } 317 else 318 { 319 return "FATAL"; 320 } 321 } 322 } 323 }